#include "mod_perseus.h"

#include "util_filter.h"
#include "apr_buckets.h"
#include "http_protocol.h"
#include "http_request.h"


module AP_MODULE_DECLARE_DATA perseus_module;



pcc * 
get_pcc(request_rec *r)
{
    server_rec      *srv;
    pcc             *aCode;
    size_t          client_ip_length;
    size_t          perseus_id_length;
    size_t          key_length; 
    const char      *perseus_id;
    unsigned char   *key;
    char            *client_ip;
    char            *message;


    if (r == NULL)
        return NULL;
    if (r->connection == NULL)
        return NULL;
    
    client_ip = r->connection->remote_ip;
    if (client_ip == NULL)
        return NULL;

    client_ip_length = strlen(client_ip);
    
    /*
     * IPv4 max 15 car
     */
    if (client_ip_length > 16)
        return NULL;

    srv = r->server;

    if (srv == NULL)
        return NULL;

    perseus_id = apr_table_get(r->headers_in, "Perseus_id");

    if (perseus_id == NULL) 
        return NULL;
    
    perseus_id_length = strlen(perseus_id);
    
    /*
     * MAX_INT = 2147483647 (10 char)
     */
    if (perseus_id_length > 10)
        return NULL;

    key_length = perseus_id_length + client_ip_length; 
    key = (unsigned char *) 
        apr_palloc(r->pool, sizeof(unsigned char) * (key_length+1));        
    
    if (key == NULL)
        return NULL;

    memcpy(key, client_ip, client_ip_length);
    memcpy(key+client_ip_length, perseus_id, perseus_id_length);
    key[key_length] = '\0';

    ap_log_error(APLOG_MARK,APLOG_ERR,0,srv,
            "Perseus hash key : %s ",key);

    message = perseus_shmht_retrieve(srv, key, key_length);
    if (message == NULL)
    {
        ap_log_error(APLOG_MARK,APLOG_ERR,0,srv,
                "ERROR Perseus hash key unkonwn : %s ",key);
        return NULL;
    }
    
    aCode=pcc_save(r->pool,message);
    if (aCode == NULL) {
        ap_log_error(APLOG_MARK,APLOG_ERR,0,srv,
                    "ERROR Perseus pcc");
    }
        ap_log_error(APLOG_MARK,APLOG_ERR,0,srv,
                    "End get pcc");
    return aCode;
}

apr_status_t 
set_pcc(request_rec *r, char *param)
{
    server_rec      *srv;
    apr_time_t      expiry;
    size_t          client_ip_length;
    size_t          perseus_id_length;
    size_t          key_length;
    apr_status_t    ret;
    const char      *perseus_id;
    unsigned char   *key; 
    char            *client_ip;
   
    if (r == NULL)
        return APR_EGENERAL;
    if (r->connection == NULL)
        return APR_EGENERAL;
    
    client_ip = r->connection->remote_ip;
    
    if (client_ip == NULL)
        return APR_EGENERAL;

    client_ip_length = strlen(client_ip);

    /*
     * IPv4 max 15 car
     */
    if (client_ip_length > 16)
        return APR_EGENERAL;

    srv = r->server;

    if (srv == NULL)
        return APR_EGENERAL;

    perseus_id = apr_table_get(r->headers_in, "Perseus_id");
    if (!perseus_id)
        return APR_EGENERAL;
    
 
    perseus_id_length = strlen(perseus_id);

    /*
     * MAX_INT = 2147483647 (10 char)
     */
    if (perseus_id_length > 10)
        return APR_EGENERAL;


    key_length = perseus_id_length + client_ip_length;
 
    key = (unsigned char *) 
        apr_palloc(r->pool, sizeof(unsigned char) * (key_length+1));        

    memcpy(key, client_ip, client_ip_length);
    memcpy(key+client_ip_length, perseus_id, perseus_id_length);
    key[key_length] = '\0';

    ap_log_error(APLOG_MARK,APLOG_ERR,0,srv,
            "Perseus hash key : %s ",key);
    
    expiry = apr_time_now() + PERSEUS_TIMEOUT;
    ret = perseus_shmht_store(srv, key,key_length, expiry, param);
   
    return ret;
}


static void *
create_perseus_config(apr_pool_t *p, server_rec *s)
{
    perseus_config *c;
    
    c = (perseus_config *) apr_pcalloc(p, sizeof(perseus_config));
    
    if (!c) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
                "Failed to allocate memory for perseus_config");
        exit(1);
    }

    c->shm_filename = (char *) apr_palloc(p,sizeof(char)*17);
    
    if (!c->shm_filename) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
                "Failed to allocate memory or shm_filename");
         exit(1);
    }
    
    apr_cpystrn(c->shm_filename, SHM_FILE, SHM_FILE_L);

    c->shm_size = 1024 * 512; /* 512 KB */

    c->cMutexFilename = (char *) apr_palloc(p,sizeof(char)*18);
    if (!c->cMutexFilename) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
                "Failed to allocate memory for cMutexFilename");
         exit(1);
    }
    apr_cpystrn(c->cMutexFilename,LOCK_FILE,LOCK_FILE_L);
    
    return c;
}
/*
 * static void *
 * merge_perseus_config(apr_pool_t *p, void *basev, void *overridesv)
 * {
 *   return NULL;
 * }  
 */

int
util_read (request_rec * r, char **rbuf)
{
  
  unsigned long length;
  int           rc; 
  int           rsize;
  size_t        len_read;
  size_t        rpos;
  char          argsbuffer[HUGE_STRING_LEN];
  
  rc = OK;

  if ((rc = ap_setup_client_block (r, REQUEST_CHUNKED_ERROR)) != OK)
    return rc;

  if (ap_should_client_block (r))
    {
      rpos = 0;
      length = r->remaining;

      *rbuf = apr_pcalloc (r->pool, length + 1);

      while ((len_read =
              ap_get_client_block (r, argsbuffer, sizeof (argsbuffer))) > 0)
        {
          if ((rpos + len_read) > length)
            rsize = length - rpos;
          else
              rsize = len_read;
          memcpy ((char *) *rbuf + rpos, argsbuffer, rsize);
          rpos += rsize;
        }
    }

  return rc;
}


static int perseus_hook_method (request_rec *r)
{
//	fprintf(stderr,"hook methode Unparsed uri : %s\nParsed Uri query : %s\n args : %s ",
			//r->unparsed_uri,r->parsed_uri.query, r->args);
//	fflush(stderr);

	const char * perseus_type = apr_table_get(r->headers_in, "Perseus");

	if (!perseus_type)
		return DECLINED;

        fprintf(stderr,"perseus hook method \n");
		fflush(stderr);

	if (!apr_strnatcmp(perseus_type,"pcc")) {


        fprintf(stderr,"Phook erseus handler, header PCC found \n");
		fflush(stderr);
		fprintf(stderr,"Method number : %u, Method : %s \n",r->method_number,r->method);
		fflush(stderr);

		if (r->method_number == M_GET) {
			char * args = r->args;
            pcc * sess; 
			unsigned int args_len = strlen(args);
			char * dataDecode;
			unsigned long long decodedLength;
            unsigned int i;
			apr_table_set(r->headers_out, "Perseus","PCC");
			/* Add ID */
			apr_table_unset(r->headers_in, "Perseus");

			fprintf(stderr," length : %u \n %s",args_len, args);
			fflush(stderr);
			sess=get_pcc(r);
			if (sess == NULL)
			{
			}
			pcc_decode(r,sess,args,args_len,&dataDecode,&decodedLength);
			fprintf(stderr,"\n Data decoded [%llu] : %s \n", decodedLength, dataDecode );
			fflush(stderr);
			for (i=0; i<decodedLength; i++)
            {
                r->args[i] = dataDecode[i];
            }
            r->args[i]='\0';
			fprintf(stderr,"Unparsed uri : %s\nParsed Uri query : %s\n",
					r->unparsed_uri,r->args);

		}
        
	}

	return DECLINED;
}

/*Handler allow to communicate with the client*/
static int perseus_handler (request_rec *r)
{
    server_rec  *s = r->server;
    pcc * sess; 
    apr_status_t ret;
    char * message;
    const char * perseus_type = apr_table_get(r->headers_in, "Perseus");

    if (!perseus_type)
        return DECLINED;
    
    fprintf(stderr,"Perseus handler, Perseus header found\n");
    fflush(stderr);
    /*
     * Client ask if we have Perseus Module
     */
    if (!apr_strnatcmp(perseus_type,"check")) {
        /*
         * We can verify the method => GET
         */
        fprintf(stderr,"Perseus handler, header ack found \n");
        fflush(stderr);
        apr_table_set(r->headers_out,"Perseus","check-ack");
        return OK;
    }

    /*
     * Client ask if we know the parameters
     */
    if (!apr_strnatcmp(perseus_type,"known")) {
        /*
         * We can verify the method => GET
         */
        fprintf(stderr,"Perseus handler, header known found \n");
        fflush(stderr);
        sess = get_pcc(r);
        if (sess == NULL)
        {
            apr_table_set(r->headers_out,"perseus","known-no");
        } else {
            apr_table_set(r->headers_out,"perseus","known-yes");
        }
        /*
         * free(sess);
         */
        return OK;
    }

    /*
     * We receive the Perseus parameters
     */
    if (!apr_strnatcmp(perseus_type,"init")) {
        /*
         * We can verify the method => POST
         */
        fprintf(stderr,"Perseus handler, header init found \n");
        fflush(stderr);

        message = NULL;
        if ( util_read (r, &message) != OK )
        {    ap_log_error(APLOG_MARK,APLOG_ERR,0,s,
                    "Error read message"); 
            apr_table_set(r->headers_out, "Perseus","init-nack");
            return OK;
        }

        ret = set_pcc(r,message);
        if ( ret != APR_SUCCESS ) {
            apr_table_set(r->headers_out, "Perseus","init-nack");
            return OK;
        }
        /*
         * We must add the parameters in the table
         */
        apr_table_set(r->headers_out, "Perseus","init-ack");
        return OK;
    }
   

    return DECLINED;
}

typedef struct perseus_ctx_t
{
    unsigned char first_time;
    char * buffer;
    unsigned int read;
    pcc * code;
    apr_bucket_brigade * bb, *proc_bb; 
} perseus_ctx;

/*
 * Input  Filter allows to decode a stream
 * ALPHA VERSION
 */ 
static apr_status_t perseus_in_filter(ap_filter_t *f,
                                      apr_bucket_brigade *bb,
                                      ap_input_mode_t mode,
                                      apr_read_type_e block,
                                      apr_off_t readbytes)
{

    apr_bucket * bkt;
    request_rec *r = f->r;
    perseus_ctx * ctx = f->ctx;
    apr_status_t ret;
    server_rec * srv = r->server;
    //perseus_config * cfg; 
     const char *perseus;
    unsigned long long  decodedLength;
    char * dataDecode;
    const char * data;
    apr_size_t len;
    apr_bucket *bucket; 
    perseus = apr_table_get(r->headers_in, "Perseus");
   
    fprintf(stderr,"Input Filter begin \n");
    fflush(stderr);
    

   /*
     * just get out of the way of things we don't want.
     */
    if (r->main) {
        ap_remove_input_filter(f);
        return ap_get_brigade(f->next, bb, mode, block, readbytes);
    }
    if (mode != AP_MODE_READBYTES) {
        ap_remove_input_filter(f);
        return ap_get_brigade(f->next, bb, mode, block, readbytes);
    }

    if (f->c->aborted) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                "Perseus aborted");
        bucket = apr_bucket_eos_create(f->c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, bucket);
        return APR_ECONNABORTED;
    }

    fprintf(stderr,"... \n");
    fflush(stderr);
    //cfg = mySrvConfig(srv);

    if (!ctx) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                "Context is empty");

    /* Check perseus headers */
        perseus = apr_table_get(r->headers_in, "Perseus");
        if (!perseus) {
            ap_remove_input_filter(f);
            return ap_get_brigade(f->next, bb, mode, block, readbytes);
        }

        if (apr_strnatcmp(perseus,"pcc")) { 
            ap_remove_input_filter(f);
            return ap_get_brigade(f->next, bb, mode, block, readbytes);
        }    
        if (r->method_number == M_GET) {
            ap_remove_input_filter(f);
            return ap_get_brigade(f->next, bb, mode, block, readbytes);
        }    

        apr_table_set(f->r->headers_out, "Perseus","PCC");
        /* Add ID */
        apr_table_unset(r->headers_in, "Perseus");

        /* Init ctx struct */
        f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
        ctx->code = get_pcc(r);;
        if (ctx->code == NULL) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,"Code pcc is null");
            ctx = 0;
            return APR_EGENERAL;
        }
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                "ALLOC");
        ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
        ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
        ctx->first_time=0;
    } else {
        if(!ctx->first_time) {
            apr_bucket *bucket = apr_bucket_eos_create(f->c->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(bb, bucket);
            return APR_ECONNABORTED;
        }
    }

    if (APR_BRIGADE_EMPTY(ctx->proc_bb)) {
        apr_brigade_cleanup(ctx->bb);
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                "proc_bb is empty");
        ret = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                "proc_bb is empty");

        if (ret != APR_SUCCESS) {
            /* What about APR_EAGAIN errors? */
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                    "get proc_bb error");
            return ret;
        }
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                "begin bucket read");

        for (bkt = APR_BRIGADE_FIRST(ctx->bb);
                bkt != APR_BRIGADE_SENTINEL(ctx->bb);
                bkt = APR_BUCKET_NEXT(bkt)) {

            if (APR_BUCKET_IS_EOS(bkt)) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                        "bkt is EOS");
                return APR_EGENERAL;
            }
            if (APR_BUCKET_IS_FLUSH(bkt)) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                        "bkt is a flush");
            }

            /* read */
            apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);

            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                    "bkt read : %u %s",len,data);
        } 
    } 

    fprintf(stderr,"End filter\n");
    fflush(stderr);
    if (data != NULL)
    {
        fprintf(stderr,"Data encoded found\n");
        fflush(stderr);
        pcc_decode(r,ctx->code,data,len,&dataDecode,&decodedLength);
        fprintf(stderr,"data decode : %s \n",dataDecode);
        fflush(stderr);
        //ctx =NULL;
        //apr_brigade_cleanup(ctx->bb);
        bucket =
            apr_bucket_transient_create(dataDecode,decodedLength, f->c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, bucket);
        return APR_SUCCESS;
    }
        return APR_SUCCESS;
}

/*
 * ALPHA VERSION 
 */
static apr_status_t perseus_out_filter(ap_filter_t *f,
                                      apr_bucket_brigade *bb)

{
    apr_status_t status = APR_SUCCESS;
    request_rec *r = f->r;
    pcc * aCode;
    apr_bucket *bucket;
    apr_bucket *bkt;
    unsigned long long lent;
    apr_size_t len;
    apr_read_type_e rblock = APR_NONBLOCK_READ;
    char * str;
    perseus_ctx * ctx = f->ctx;
	    
    const char * perseus; 
    perseus = apr_table_get(r->headers_out, "Perseus");
    if (!perseus) {
        ap_remove_input_filter(f);
        return ap_pass_brigade(f->next, bb);
    }

    fprintf(stderr,"OUTPUT FILTER Header Perseus found : [%s] \n",perseus);
    fflush(stderr);
    if (apr_strnatcmp(perseus,"PCC")) { 
        ap_remove_input_filter(f);
        return ap_pass_brigade(f->next, bb);
    }    


    fprintf(stderr,"out put begin \n");
    fflush(stderr);

    if (!ctx) {
	    if ( r->main) {
		ap_remove_output_filter(f);
		return ap_pass_brigade(f->next, bb);
	    }

	    /* Do nothing if asked to filter nothing. */
	    if (APR_BRIGADE_EMPTY(bb)) {
		    ap_remove_output_filter(f);
		    return ap_pass_brigade(f->next, bb);
	    }
	    if (!ap_is_initial_req(r)) {
		    ap_remove_output_filter(f);
		    return ap_pass_brigade(f->next, bb);
	    }

/*	    if (!apr_table_get(r->headers_out, "Perseus")) {
		    ap_remove_output_filter(f);
		    return ap_pass_brigade(f->next, bb);
	    }
*/
	    ctx = f->ctx = apr_pcalloc(r->pool, sizeof(perseus_ctx));
	    ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
	    ctx->buffer = apr_palloc(r->pool, 8096);
	    ctx->read = 0;
    }  
    while (!APR_BRIGADE_EMPTY(bb))
    {
	    const char *data = NULL;
        bucket = APR_BRIGADE_FIRST(bb);
	if (APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)) {
		if (APR_BUCKET_IS_EOS(bucket)) {
			if (ctx->read != 0) {
				aCode=get_pcc(r);
				if (aCode == NULL)
				{
					ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
							"get code error");
				}
				ctx->buffer[ctx->read]='\0';
                fprintf(stderr,	" Data to encode [%u] : %s \n", ctx->read, ctx->buffer);
                fflush(stderr);
				pcc_Code(r,aCode,ctx->buffer,ctx->read,&str,&lent);
				fprintf(stderr,"\n Data encoded [%llu] : %s \n", lent, str);
				fflush(stderr);
				bkt = apr_bucket_heap_create(str, lent,
						NULL, f->c->bucket_alloc);

				APR_BRIGADE_INSERT_TAIL(ctx->bb, bkt);
				APR_BUCKET_REMOVE(bucket);
				APR_BRIGADE_INSERT_TAIL(ctx->bb,bucket);
				return ap_pass_brigade(f->next,ctx->bb);
			}
			return ap_pass_brigade(f->next,bb);
		}
		else {
			apr_bucket_delete(bucket);
		}

        }
        else {
		unsigned int i;
            status = apr_bucket_read(bucket, &data, &len, rblock);
	        //fprintf(stderr,"Data read [%u], %s\n",len,data);
            //fflush(stderr);
	/* Add data to buffer */
            
            memcpy((void *)(ctx->buffer+ctx->read),data,len);
            ctx->read += len;
	        fprintf(stderr,"Data read [%u], %s\n",ctx->read,ctx->buffer);
            fflush(stderr);
            /*for (i=0; i<len; i++) {
                ctx->buffer[ctx->read] = data[i];
                ctx->read = ctx->read+1;
            }*/

        }
        apr_bucket_delete(bucket);
    }
    fprintf(stderr,"Exit Ouput Filter\n");
    fflush(stderr);
    return APR_SUCCESS;
}

apr_status_t
cleanup_resources(void * s)
{
    
    server_rec *base_server = (server_rec *) s;
    ap_log_error(APLOG_MARK,APLOG_ERR,0,0,"Perseus cleanup resources");

    perseus_shmht_kill(base_server); 
    return APR_SUCCESS;
}


int perseus_init_Module(apr_pool_t *p, apr_pool_t *plog,
                        apr_pool_t *ptemp,
                        server_rec *base_server)
{
    void *data;
    const char *userdata_key = "perseus_init";
    apr_status_t ret;

    perseus_config *c = (perseus_config *)
        ap_get_module_config(base_server->module_config, &perseus_module);
   
   plog = plog;
   ptemp = ptemp;
    ap_log_error(APLOG_MARK,APLOG_ERR,0,base_server,"Perseus init module");
    if (!c) 
    {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, base_server,
                "Failed to get perseus_config");
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    
    /* initialize_module() will be called twice, and if it's a DSO
     * then all static data from the first call will be lost. Only
     * set up our static data on the second call. */
    apr_pool_userdata_get(&data, userdata_key, base_server->process->pool);
    if (!data) {
        apr_pool_userdata_set((const void *)1, userdata_key,
                               apr_pool_cleanup_null, base_server->process->pool);
        return OK;
    }
    
    ret = perseus_shmht_init(base_server,p);
    if ( ret != APR_SUCCESS)
        return HTTP_INTERNAL_SERVER_ERROR;
    
    /* Register a cleanup function */
   apr_pool_cleanup_register(p, base_server,
                             cleanup_resources,
                             apr_pool_cleanup_null);
   
   return 0;
}


static void register_hooks(apr_pool_t *p)
{
    p = p;
    ap_hook_post_config(perseus_init_Module, NULL,NULL, APR_HOOK_MIDDLE);
    ap_hook_translate_name(perseus_hook_method, NULL, NULL, APR_HOOK_REALLY_FIRST);
    ap_hook_handler(perseus_handler, NULL, NULL,APR_HOOK_REALLY_FIRST);
    ap_register_input_filter(PerseusFilterName, perseus_in_filter, NULL,AP_FTYPE_TRANSCODE);
    //AP_FTYPE_CONNECTION+4);
    ap_register_output_filter(PerseusFilterName, perseus_out_filter, NULL,
            AP_FTYPE_CONTENT_SET-1 );
  return;
}


module AP_MODULE_DECLARE_DATA perseus_module = {
    STANDARD20_MODULE_STUFF,
    NULL,                           /* dir config creater */
    NULL,                           /* dir merger --- default is to override */
    create_perseus_config,          /* server config */
    NULL,           /* merge server config */
    NULL,                           /* command table */
    register_hooks                  /* register hooks */
};
